/*   Copyright (C) 2011 Wolfgang Hankeln, Norma J. Wendel
 *
 *   This file is part of CDinFusion.
 *
 *   CDinFusion is free software: you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public 
 *   License in version 3 (LGPL3) as published by the Free Software
 *   Foundation version 3 of the License.
 *
 *   CDinFusion is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   Lesser General Public License for more details.
 *
 *   You should have received a copy of the Lesser General Public
 *   License along with CDinFusion; see the file COPYING. If not,
 *   write to the Free Software Foundation, 59 Temple Place - Suite 330,
 *   Boston, MA 02111-1307, USA or see <http://www.gnu.org/licenses/>.
 *   
 *   If you have any questions, send an email to: megx@mpi-bremen.de.
 */
package org.cdinfusion.filehandling;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.apache.commons.io.IOUtils;
import org.cdinfusion.data.CDElement;
import org.cdinfusion.data.CDElementHelper;
import org.cdinfusion.data.GetInformation;

import de.mpi_bremen.mgg.FastaValidator.FastaHandlingException;
import de.mpi_bremen.mgg.FastaValidator.FastaValidatorCallback;

public final class FastaAndStruturedCommentWriter implements
		FastaValidatorCallback {

	private final Pattern HEADER_PATTERN = Pattern.compile("^>(\\S+)\\s*(.*)$");

	private final List<CDElement> cdElements;
	private final String sessionID;
	private final String folder;
	private final BufferedWriter fastaWriter;
	private BufferedWriter structuredCommentWriter;
	private int currentCDElementIndex;
	private String currentCDHeader = null;
	private String currentStructuredCommentValues;
	private boolean validHeader;

	public FastaAndStruturedCommentWriter(final String folder,
			final String sessionID, final List<CDElement> cdElements)
			throws IOException {
		this.cdElements = cdElements;
		this.folder = folder;
		this.sessionID = sessionID;
		this.fastaWriter = new BufferedWriter(new FileWriter(new File(folder,
				"cdfile_" + sessionID + ".fasta")));
		this.currentCDElementIndex = 0;
	}

	public void commentline(final String comment) throws FastaHandlingException {
		// ignored
	}

	public void eof() throws FastaHandlingException {
		try {
			// handle structured comment files
			this.structuredCommentWriter.flush();
			this.zipStructuredCommentFiles();

			// handle fasta files
			this.fastaWriter.flush();
			final File destfile = new File(this.folder, "raw_" + this.sessionID
					+ ".fasta");
			System.out
					.println("Your file has been written... delete old file: "
							+ destfile.getName());
			destfile.delete();
		} catch (final IOException e) {
		    throw new FastaHandlingException("IO error while generating files.", e);
		} finally {
		    IOUtils.closeQuietly(this.fastaWriter);
		    IOUtils.closeQuietly(this.structuredCommentWriter);
		}
	}

	public void header(final String header) throws FastaHandlingException {
		try {
			final Matcher matcher = this.HEADER_PATTERN.matcher(header);
			this.validHeader = matcher.matches();
			if (this.validHeader) {
				final String sequenceID = matcher.group(1);

				this.writeHeaderToFasta(sequenceID, header);
				this.writeHeaderToStructuredComment();
				this.writeSequenceIdToStructuredComment(sequenceID);

				this.updateCurrentCDElement(sequenceID);
			}
		} catch (final IOException e) {
		    throw new FastaHandlingException("IO error while generating files.", e);
		} catch (final Exception e) {
		    // writeHeaderToStructuredComment throws a lot of exceptions
		    // because reflection is used. This exceptions should be
		    // handled all in the same way, but handling multiple
		    // exceptions in one catch is not possible in Java prior
		    // to version 7. Therefore catch Exception is used -
		    // unfortunately Exception is the most specific common
		    // ancestor of the thrown exceptions.
		    throw new FastaHandlingException("An internal error occured.", e);
		}
	}

	public void seqline(final String line) throws FastaHandlingException {
	    if (this.validHeader) {
		try {
		    this.fastaWriter.write(line);
		    this.fastaWriter.newLine();
		} catch (final IOException e) {
		    throw new FastaHandlingException("IO error while generating files.", e);
		}
	    }
	}
	
	private void writeHeaderToStructuredComment() throws IOException,
			IllegalArgumentException, SecurityException,
			IllegalAccessException, InvocationTargetException,
			NoSuchMethodException {

		if (this.currentStructuredCommentValues == null) {

			// close old file and open new file
			if (this.structuredCommentWriter != null) {
				IOUtils.closeQuietly(this.structuredCommentWriter);
			}
			this.structuredCommentWriter = new BufferedWriter(new FileWriter(
					new File(this.folder, "sequin_"
							+ this.currentCDElementIndex + "_" + this.sessionID
							+ ".cmt")));

			// write header to file
			final CDElement cdElement = this.cdElements
					.get(this.currentCDElementIndex);
			this.structuredCommentWriter.write("SeqID\t");
			this.structuredCommentWriter.write("type of report\t");
			this.structuredCommentWriter.write(GetInformation
					.keyInformation(cdElement.getStudy()));
			this.structuredCommentWriter.write("environmental package\t");
			this.structuredCommentWriter.write(GetInformation
					.keyInformation(cdElement.getEnv()));
			this.structuredCommentWriter.write("created with");
			this.structuredCommentWriter.newLine();

			// generate contextual data for each seqeunce entry
			final StringBuilder builder = new StringBuilder(1000);
			builder.append(GetInformation.getLongNameForStudy(cdElement
					.getStudy()));
			builder.append("\t");
			builder.append(GetInformation.valueInformation(cdElement.getStudy()));
			builder.append(GetInformation.getLongNameForEnvironment(cdElement.getEnv()));
			builder.append("\t");
			builder.append(GetInformation.valueInformation(cdElement.getEnv()));
			this.currentStructuredCommentValues = builder.toString();
		}
	}

	private void writeSequenceIdToStructuredComment(final String sequenceID)
			throws IOException {
		this.structuredCommentWriter.write(sequenceID);
		this.structuredCommentWriter.write("\t");
		this.structuredCommentWriter.write(this.currentStructuredCommentValues);
		this.structuredCommentWriter.write("CDinFusion");
		this.structuredCommentWriter.newLine();
	}

	private void writeHeaderToFasta(final String sequenceID, final String header)
			throws IOException {
		final CDElement cdElement = this.cdElements
				.get(this.currentCDElementIndex);

		if (cdElement.isKeepheader()) {
			this.fastaWriter.write(header);
		} else {
			if (this.currentCDHeader == null) {
				this.currentCDHeader = CDElementHelper
						.generateHeader(cdElement);
			}
			this.fastaWriter.write(">");
			this.fastaWriter.write(sequenceID);
			this.fastaWriter.write(" ");
			this.fastaWriter.write(this.currentCDHeader);
		}

		this.fastaWriter.newLine();
	}

	private void updateCurrentCDElement(final String header) {
		if (this.cdElements.get(this.currentCDElementIndex).getLastSequenceID()
				.equals(header)) {
			this.currentCDElementIndex++;
			this.currentCDHeader = null;
			this.currentStructuredCommentValues = null;
		}
	}


	private void zipStructuredCommentFiles() throws IOException {
		if (this.cdElements.size() > 1) {
			ZipOutputStream out = null;
			FileInputStream in = null;

			try {

				out = new ZipOutputStream(new FileOutputStream(new File(
						this.folder, "cmt_files_" + this.sessionID + ".zip")));
				final Pattern pattern = Pattern.compile("sequin_[0-9]+_"
						+ this.sessionID + "\\.cmt");
				final File[] files = new File(this.folder)
						.listFiles(new FileFilter() {
							public boolean accept(final File pathname) {
								return pattern.matcher(pathname.getName())
										.matches();
							}
						});

				for (int fileIndex = 0; fileIndex < files.length; fileIndex++) {
					/* read data from file and write to ZipEntry */
					in = new FileInputStream(files[fileIndex]);
					out.putNextEntry(new ZipEntry(files[fileIndex].getName()));
					IOUtils.copy(in, out);
					out.closeEntry();
					in.close();
				}
				
				for (int fileIndex = 0; fileIndex < files.length; fileIndex++) {
				    if (!files[fileIndex].delete()) {
					System.out.printf("File %s was added to the ZIP file but could not be deleted afterwards.%n", 
						files[fileIndex].getAbsolutePath());
				    }
				}
				
			} finally {
				IOUtils.closeQuietly(in);
				IOUtils.closeQuietly(out);
			}
		}
	}
}
